#include <QtGui>
#include <QToolTip>
#include <QDomDocument>
#include <QDomElement>
#include <sstream>
#include "Config.h"
#include "Viboard.h"
#include "FakeJtag.h"

using namespace etb;

Viboard::Viboard(EventBus* bus, QWidget *parent, Qt::WindowFlags flags)
  : QMainWindow(parent, flags), bus_(bus), jtag_(NULL), lcdAddr_(0), 
    running_(false), lastHovered_(NULL)
{
  timer_ = new QTimer(this);
  connect(timer_, SIGNAL(timeout()), this, SLOT(updateLcd()));
}

Viboard::~Viboard()
{
  std::map<QString, QImage*>::iterator iter;
  std::vector<HotArea*>::iterator iter1;
  
  for (iter1 = areas_.begin(); iter1 != areas_.end(); ++iter1)
    if (*iter1) delete (*iter1);
}

void Viboard::init()
{
  if (bus_)
    bus_->registerEventClient(this);

  QDomDocument xmlDoc;
  QFile xmlFile(makeFilePath(BOARD_XML_PATH, BOARD_XML_FILE));
  ASSERT(xmlFile.open(QIODevice::ReadOnly));
  if (!xmlDoc.setContent(&xmlFile)) {
    xmlFile.close();
    abort();
  }
  xmlFile.close();

  QDomElement rootElem = xmlDoc.documentElement();
  QString size = rootElem.attribute("size");
  if (!size.isEmpty()) {
    QStringList ss = size.split("x");
    if (ss.count() != 2) {
      MSG("Tag <board> attr size has wrong value. Ignore window size setting.");
    }

    int width = ss[0].toInt();
    int height = ss[1].toInt();

    if (width > 0 && height > 0) {
      // for status bar
      setFixedSize(width, height + 23);
      setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));

      // Add a horizontal line to separate the board and status bar
      QFrame *sline = new QFrame(this);
      sline->setGeometry(QRect(0, height + 1, width, 1));
      sline->setFrameShape(QFrame::HLine);
      sline->setFrameShadow(QFrame::Sunken);
      sline->show();
    }
  }

  QDomNode n = rootElem.firstChild();

  while (!n.isNull()) {
    QDomElement e = n.toElement(); // try to convert the node to an element.
    if(!e.isNull()) {
      if (e.tagName() == "states") {
        // states element
        parseXmlStates(n);
      } else if (e.tagName() == "device") {
        // device element
        parseXmlDevice(n);
      }
    }
    n = n.nextSibling();
  }

  // Save normal status image in buffer
  QImage img = status_[defaultStatus_];
  bufferImg_ = img.copy();

  // update
  update();
  //dumpAreas();

  // Activate mouse move event
  setMouseTracking(true);

  // Show status bar
  statusBar()->showMessage(tr("Welcome to Emulated Target Board v1.0!"));
  QTimer::singleShot(2000, this, SLOT(updateStatusBar()));

  // Start refresh the LCD
  timer_->start(20);
}

// Parse <states> tag in xml file
// save status and load images to status_
void Viboard::parseXmlStates(QDomNode& node)
{
  QDomNode n = node.firstChild();

  while (!n.isNull()) {
    QDomElement e = n.toElement();
    if (!e.isNull()) {
      if (e.tagName() == "status") {
        QString name = e.attribute("name");
        QString filename = e.attribute("file");
        bool isDefault = (e.attribute("default") == "true");

        if (!name.isEmpty() && !filename.isEmpty()) {
          // Loading image file failed will make this entry ignored
          QImage img = 
              QImage(makeFilePath(BOARD_XML_PATH, filename)).convertToFormat(QImage::Format_RGB32);
          if (!img.isNull()) {
            if (status_.empty()) {
              defaultStatus_ = name;
            }

            status_[name] = img;
            MSG("<states> entry (" << qPrintable(name) << ", " 
                << qPrintable(filename) << ") load success.");

            if (isDefault)
              defaultStatus_ = name;

          } else {
            MSG("<status> entry (" << qPrintable(name) << ", " 
                << qPrintable(filename) << ") load image failed.");
          }
        } else {
          MSG("<statuss> entry (" << qPrintable(name) << ", " 
              << qPrintable(filename) << ") ignored.");
        }
      }
    }
    n = n.nextSibling();
  }

  MSG("<states> entry (" << qPrintable(defaultStatus_) << ") makes default.");
}

void Viboard::parseXmlDevice(QDomNode& node)
{
  QDomElement rootElem = node.toElement();

  if (!rootElem.isNull()) {
    QString name = rootElem.attribute("name");
    if (!name.isEmpty()) {
      HotArea* ha = new HotArea(name, defaultStatus_);
      ha->focusable_ = (rootElem.attribute("focusable") == "true");
      areas_.push_back(ha);

      QDomNode n = node.firstChild();
      while (!n.isNull()) {
        QDomElement e = n.toElement();
        if (!e.isNull()) {
          if (e.tagName() == "geometry") {
            ha->region_.x = e.attribute("x").toInt();
            ha->region_.y = e.attribute("y").toInt();
            ha->region_.w = e.attribute("w").toInt();
            ha->region_.h = e.attribute("h").toInt();
          } else if (e.tagName() == "tips") {
            ha->tips_ = e.text();
          } else if (e.tagName() == "on-event") {
            ha->addEvent(e.attribute("name"), e.attribute("status"));
          } else if (e.tagName() == "press-action") {
            ha->addAction("press", e.attribute("status"), ETB_CMD_PARAM_PRESS);
          } else if (e.tagName() == "release-action") {
            ha->addAction("release", e.attribute("status"), ETB_CMD_PARAM_RELEASE);
          } else if (e.tagName() == "toggle-action") {
            ha->togglable_ = true;
            parseXmlDeviceToggle(n, ha);
          }
        }
        n = n.nextSibling();
      }
    }
  }
}

void Viboard::parseXmlDeviceToggle(QDomNode& node, HotArea* ha)
{
  QDomNode n = node.firstChild();
  while (!n.isNull()) {
    QDomElement e = n.toElement();

    if (!e.isNull()) {
      if (e.tagName() == "status") {
        ha->addToggleEvent(e.attribute("name"), e.attribute("event"));
      }
    }

    n = n.nextSibling();
  }
}

void Viboard::redrawArea(HotArea *ha)
{
  if (!ha) return;

  // Get clip on new status
  QImage bigImg = status_[ha->status_];
  QRect r(ha->region_.x, ha->region_.y, ha->region_.w, ha->region_.h);
  r = r.adjusted(-2,-2,2,2);
  QImage clip = bigImg.copy(r);

  // Paint on buffer image
  QPainter painter(&bufferImg_);
  painter.drawImage(r, clip, clip.rect());

  // Check to draw border to show hover state
  if (ha->hovered_) {
    //MSG("draw border: " << r.x() << "," << r.y() 
    //    << "," << r.width() << "," << r.height());
    QPen p = painter.pen();
    QPen pen(Qt::darkGreen, 2, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);
    painter.setPen(pen);
    painter.drawRect(r.adjusted(1,1,-1,-1));
    painter.setPen(p);
  }

  // Update the canvas
  update(r);
}

void Viboard::updateLcd()
{
  if (lcdAddr_) {
    HotArea* ha = findAreaByName("lcd");
    if (ha) {
      QRect r(ha->region_.x, ha->region_.y, ha->region_.w, ha->region_.h);
      QImage clip(reinterpret_cast<const unsigned char*>(lcdAddr_), 
                  240, 320, QImage::Format_RGB32);

      QPainter painter(&bufferImg_);
      painter.drawImage(r, clip, clip.rect());
      update(r);
    }
  }
}

void Viboard::updateStatusBar()
{
  statusBar()->showMessage(tr("Click CPU to show FakeJtag window."));
}

Viboard::HotArea* Viboard::findAreaByPos(int x, int y)
{
  std::vector<HotArea*>::const_iterator iter;
  for (iter = areas_.begin(); iter != areas_.end(); ++iter) {
    if (!isOutBoundArea(x, y, *iter)) return *iter;
  }

  return NULL;
}

Viboard::HotArea* Viboard::findAreaByName(QString name)
{
  std::vector<HotArea*>::const_iterator iter;
  for (iter = areas_.begin(); iter != areas_.end(); ++iter) {
    if ((*iter)->name_ == name) return *iter;
  }

  return NULL;
}

bool Viboard::isOutBoundArea(int x, int y, HotArea *ha)
{
  if (!ha) return false;

  int x1 = ha->region_.x;
  int y1 = ha->region_.y;
  int x2 = ha->region_.x + ha->region_.w;
  int y2 = ha->region_.y + ha->region_.h;

  return ( (x > x2) || (x < x1) || (y > y2) || (y < y1) );
}

void Viboard::paintEvent(QPaintEvent *event)
{
  QPainter painter(this);
  //MSG("paintEvent on " << event->rect().x() << "," << event->rect().y() 
  //    << "," << event->rect().width() << "," << event->rect().height())
  painter.drawImage(event->rect(), bufferImg_, event->rect());
}

void Viboard::mouseMoveEvent(QMouseEvent *event)
{
  // Check last hovered device, toggle state
  QPoint pos = event->pos();
  if (lastHovered_ && isOutBoundArea(pos.x(), pos.y(), lastHovered_)) {
    lastHovered_->hovered_ = false;
    redrawArea(lastHovered_);
    //std::cout << "clear last hovered: " << qPrintable(lastHovered_->name_) << std::endl;
    lastHovered_ = NULL;
    QToolTip::hideText();
    setCursor(Qt::ArrowCursor);
  }

  // Draw the new hovered device hover state
  HotArea *ha = findAreaByPos(pos.x(), pos.y());
  if (ha && ha->focusable_ && (lastHovered_ != ha)) {
    //std::cout << "(" << pos.x() << "," << pos.y() << "): " << qPrintable(ha->name_) << std::endl;
    ha->hovered_ = true;
    lastHovered_ = ha;
    redrawArea(ha);
    setCursor(Qt::PointingHandCursor);
  }

  // show tips
  if (ha)
    //QToolTip::showText(event->globalPos(), ha->tips_, this, this->rect());
    statusBar()->showMessage(ha->tips_);
}

void Viboard::mousePressEvent(QMouseEvent *event)
{
  QPoint pos = event->pos();
  HotArea *ha = findAreaByPos(pos.x(), pos.y());

  if (ha && ha->focusable_) {
    QString status, genEvent;
    ha->getActionResult("press", status, genEvent);
    if (!status.isEmpty()) {
      ha->status_ = status;
      if (!genEvent.isEmpty())
        sendEvent(ha->name_, genEvent);
      redrawArea(ha);
    }
  }
}

void Viboard::mouseReleaseEvent(QMouseEvent *event)
{
  QPoint pos = event->pos();
  HotArea *ha = findAreaByPos(pos.x(), pos.y());

  if (ha && ha->focusable_) {
    QString status, genEvent;
    ha->getActionResult("release", status, genEvent);
    if (!status.isEmpty()) {
      ha->status_ = status;
      if (!genEvent.isEmpty()) {
        if (ha->name_ == ETB_CMD_DEV_CPU && genEvent == ETB_CMD_PARAM_RELEASE)
          if (jtag_->isVisible()) jtag_->hide();
          else jtag_->show();
        else 
          sendEvent(ha->name_, genEvent);
      }
      redrawArea(ha);
    }
  }
}

void Viboard::mouseDoubleClickEvent(QMouseEvent *event)
{
  QPoint pos = event->pos();
  HotArea *ha = findAreaByPos(pos.x(), pos.y());

  if (ha && ha->focusable_) {
    QString status, genEvent;
    ha->getToggleResult(status, genEvent);
    if (!status.isEmpty()) {
      ha->status_ = status;
      if (!genEvent.isEmpty())
        sendEvent(ha->name_, genEvent);
      redrawArea(ha);
    }
  }
}

void Viboard::sendEvent(QString deviceName, QString event)
{
  bus_->sendEvent(
      makeCmd(ETB_CMD_TARGET_EM, qPrintable(deviceName), 
              qPrintable(event)) );
}

void Viboard::closeEvent(QCloseEvent *event)
{
  event->ignore();

  if (running_) {
    QMessageBox *msgBox = new QMessageBox(this);
    msgBox->setWindowTitle("Warning");
    msgBox->setText("Machine is running, Are you sure you want to exit?");   

    QPushButton *yesButton = msgBox->addButton(tr("Yes"), QMessageBox::ActionRole);
    msgBox->addButton(tr("No"), QMessageBox::ActionRole);
    msgBox->exec();

    if ((QPushButton*)msgBox->clickedButton() == yesButton) {
      if (jtag_) jtag_->close();
      event->accept();
    }
  } else {
    if (jtag_) jtag_->close();
    event->accept();
  }
}

void Viboard::handleEvent(const std::string event)
{
  if (event.empty()) return;

  EventStruct es;
  parseEvent(event, &es);

  if (es.target.compare(ETB_CMD_TARGET_VI) == 0) {
    MSG("[VIBOARD] event received - " << event);

    if (es.device.compare(ETB_CMD_DEV_LCD)  == 0) {
      if (es.param.compare(0, 4, ETB_CMD_PARAM_ADDR) == 0) {
        lcdAddr_ = reinterpret_cast<U8*>(hexStrToAddr(es.param.substr(5)));
      }
    } else {
      HotArea* ha = findAreaByName(es.device.c_str());
      if (ha) {
        QString status;
        ha->getEventResult(es.param.c_str(), status);
        if (!status.isEmpty()) {
          ha->status_ = status;
          redrawArea(ha);
        }
      } else if (es.device.compare(ETB_CMD_DEV_STATE) == 0) {
        if (es.param.compare(ETB_CMD_PARAM_RUN) == 0) {
          running_ = true;
        } else if (es.param.compare(ETB_CMD_PARAM_STOP) == 0) {
          running_ = false;
          clearAllAreaStatus();
        }
      }
    }
  }
}

void Viboard::clearAllAreaStatus()
{
  // Set all HotArea status to normal
  std::vector<HotArea*>::const_iterator iter;
  for (iter = areas_.begin(); iter != areas_.end(); ++iter) {
    if ((*iter)->status_ != defaultStatus_) {
      (*iter)->status_ = defaultStatus_;
      redrawArea(*iter);
    }
  }
}

U64 Viboard::hexStrToAddr(const std::string str)
{
  U64 addr;
  std::stringstream ss;
  ss << std::hex << str;
  ss >> addr;

  return addr;
}

void Viboard::dumpAreas()
{
  std::vector<HotArea*>::const_iterator iter1;
  for (iter1 = areas_.begin(); iter1 != areas_.end(); ++iter1) {
    std::cout << qPrintable((*iter1)->name_) << "(" << (*iter1)->region_.x 
        << ", " << (*iter1)->region_.y << ", " << (*iter1)->region_.w
        << ", " << (*iter1)->region_.h << ")" << std::endl;
    std::cout << "  events: ";

    std::vector<HotArea::HotAreaEvent*>::const_iterator iter2 = (*iter1)->events_.begin();
    for (;iter2 != (*iter1)->events_.end(); ++iter2) {
      std::cout << qPrintable((*iter2)->event) << "-" << qPrintable((*iter2)->status) << " ";
    }
    std::cout << std::endl;

    std::cout << "  actions: ";
    std::vector<HotArea::HotAreaAction*>::const_iterator iter3 = (*iter1)->actions_.begin();
    for (;iter3 != (*iter1)->actions_.end(); ++iter3) {
      std::cout << qPrintable((*iter3)->action) << "-" 
          << qPrintable((*iter3)->status) << "-" 
          << qPrintable((*iter3)->genEvent) << " ";
    }
    std::cout << std::endl;

    std::cout << "  toggles: ";

    std::vector<HotArea::HotAreaToggle*>::const_iterator iter4 = (*iter1)->toggles_.begin();
    for (;iter4 != (*iter1)->toggles_.end(); ++iter4) {
      std::cout << qPrintable((*iter4)->status) << "-" << qPrintable((*iter4)->genEvent) << " ";
    }
    std::cout << std::endl;


    std::cout << "  tips: ";
    std::cout << qPrintable((*iter1)->tips_);
    std::cout << std::endl;
  }
}

QString Viboard::makeFilePath(QString path1, QString path2)
{
#ifndef __MINGW32__
  return QString(path1) + "/" + path2;
#else
  return QString(path1) + "\\" + path2;
#endif
}

Viboard::HotArea::HotArea(QString name, QString status)
  : name_(name), status_(status), hovered_(false), togglable_(false)
{
}

Viboard::HotArea::~HotArea()
{
  {
    std::vector<HotAreaEvent*>::iterator iter;
    for (iter = events_.begin(); iter != events_.end(); ++iter)
      if (*iter) delete (*iter);
  }

  {
    std::vector<HotAreaToggle*>::iterator iter;
    for (iter = toggles_.begin(); iter != toggles_.end(); ++iter)
      if (*iter) delete (*iter);
  }

  {
    std::vector<HotAreaAction*>::iterator iter;
    for (iter = actions_.begin(); iter != actions_.end(); ++iter)
      if (*iter) delete (*iter);
  }

}

void Viboard::HotArea::addEvent(QString event, QString status)
{
  if (event.isEmpty()) return;

  HotAreaEvent *e = new HotAreaEvent();
  e->event = event;
  e->status = status;

  events_.push_back(e);
}

void Viboard::HotArea::addToggleEvent(QString status, QString genEvent)
{
  if (status.isEmpty()) return;

  HotAreaToggle *t = new HotAreaToggle();
  t->genEvent = genEvent;
  t->status = status;

  toggles_.push_back(t);
}

void Viboard::HotArea::addAction(QString action, QString status, QString genEvent)
{
  if (action.isEmpty()) return;

  HotAreaAction *a = new HotAreaAction();
  a->action = action;
  a->status = status;
  a->genEvent = genEvent;

  actions_.push_back(a);
}

void Viboard::HotArea::getEventResult(QString event, QString& status)
{
  std::vector<HotAreaEvent*>::iterator iter;
  for (iter = events_.begin(); iter != events_.end(); ++iter) {
    if ((*iter)->event == event) {
      status = (*iter)->status;
      break;
    }
  }
}

void Viboard::HotArea::getActionResult(QString action, QString& status, QString& genEvent)
{
  std::vector<HotAreaAction*>::iterator iter;
  for (iter = actions_.begin(); iter != actions_.end(); ++iter) {
    if ((*iter)->action == action) {
      status = (*iter)->status;
      genEvent = (*iter)->genEvent;
      break;
    }
  }
}

void Viboard::HotArea::getToggleResult(QString& status, QString& genEvent)
{
  // Change status among toggle data one by one
  std::vector<HotAreaToggle*>::iterator iter;
  for (iter = toggles_.begin(); iter != toggles_.end(); ++iter) {
    if ((*iter)->status == status_) {
      iter++;
      if (iter == toggles_.end()) {
        iter = toggles_.begin();
      }

      status = (*iter)->status;
      genEvent = (*iter)->genEvent;

      break;
    }
  }
}
